package com.siwei.spatial.service.file.impl;

import com.alibaba.fastjson2.JSON;
import com.siwei.common.core.utils.StringUtils;
import com.siwei.common.core.utils.uuid.IdUtils;
import com.siwei.spatial.api.domain.file.TGeomDb;
import com.siwei.spatial.api.domain.file.TGeomDbDetails;
import com.siwei.spatial.service.file.ISpatialFilesDbService;
import com.siwei.spatial.service.file.ITGeomDbDetailsService;
import com.siwei.spatial.service.file.ITGeomDbService;
import com.siwei.spatial.utils.FileUtils;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

@Service
public class SpatialFilesDbServiceImpl implements ISpatialFilesDbService {
    private static final Logger log = LoggerFactory.getLogger(SpatialFilesDbServiceImpl.class);

    // /**
    //  * 上传文件存储在本地的根路径
    //  */
    // @Value("${file.path}")
    // private String localFilePath;
    /**
     * 入库的坐标系
     */
    @Value("${shp.db_srid}")
    private Integer shpDbSRID;
    /**
     * 面积计算坐标系
     */
    @Value("${shp.area_srid}")
    private Integer shpAreaSRID;
    /**
     * 面积计算是否启用geography true启用 false不启用
     */
    @Value("${shp.area_geography}")
    private Boolean shpAreaGeography;
    /**
     * 输出坐标系
     */
    @Value("${shp.out_srid}")
    private Integer shpOutSRID;
    @Autowired
    private ITGeomDbDetailsService itGeomDbDetailsService;
    @Autowired
    private ITGeomDbService itGeomDbService;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public TGeomDb readShpFile(String shpFileName, String shpFilePath, String fromRoute) throws IOException {
        if (StringUtils.isEmpty(shpFilePath)) {
            log.error("读取SHP文件路径失败");
            return null;
        }
        String shpDir = shpFilePath.substring(0, shpFilePath.lastIndexOf(".zip"));
        FileUtils.unzipAutoCharset(shpFilePath, shpDir);
        List<Map<String, String>> shpFiles = FileUtils.readShpFiles(shpDir);

        if (shpFiles.isEmpty()) {
            log.error("读取SHP文件失败");
            return null;
        }
        // TODO 目前只用了一个.shp文件，后续如果有多个shp文件，可能需要处理
        Map<String, String> shpFileMap = shpFiles.get(0);
        File shpFile = new File(shpFileMap.get("shp"));
        File dbfFile = new File(shpFileMap.get("dbf"));
        File shxFile = new File(shpFileMap.get("shx"));
        File prjFile = new File(shpFileMap.get("prj"));
        if (!shpFile.exists() || !dbfFile.exists() || !shxFile.exists()) {
            log.error("读取SHP文件缺失");
            return null;
        }

        if (StringUtils.isEmpty(shpFileName)) {
            shpFileName = shpFile.getName().substring(0, shpFileName.length() - 4);
        }
        TGeomDb dto = new TGeomDb();
        dto.setId(IdUtils.fastSimpleUUID());
        dto.setName(shpFileName);
        dto.setShppath(shpFileMap.get("shp"));
        dto.setFiletype("2");
        dto.setFromroute(fromRoute);

        List<Map<String, Object>> shpFeaturesList = readShapeFile(shpFile);
        Long sort = 0L;
        for (Map<String, Object> shpFeatures : shpFeaturesList) {
            TGeomDbDetails detailsDTO = new TGeomDbDetails();
            detailsDTO.setShpDbSRID(shpDbSRID);
            detailsDTO.setShpAreaSRID(shpAreaSRID);
            detailsDTO.setShpOutSRID(shpOutSRID);
            detailsDTO.setShpAreaGeography(shpAreaGeography);
            detailsDTO.setId(IdUtils.fastSimpleUUID());
            detailsDTO.setUploadId(dto.getId());
            detailsDTO.setGeom((String) shpFeatures.get("the_geom"));
            shpFeatures.remove("the_geom");
            detailsDTO.setSort(sort);
            detailsDTO.setGeomJson(JSON.toJSONString(shpFeatures));
            shpFeatures.put("id", detailsDTO.getId());
            itGeomDbDetailsService.insertTGeomDbDetails(detailsDTO);
            sort++;
        }
        TGeomDb retDto = itGeomDbDetailsService.sumGeomDetailsByUploadId(dto.getId());
        dto.setGeom(retDto.getGeom());
        dto.setGeomNumber(retDto.getGeomNumber());
        dto.setGeomArea(retDto.getGeomArea());
        dto.setShpDbSRID(shpDbSRID);
        dto.setShpAreaSRID(shpAreaSRID);
        dto.setShpOutSRID(shpOutSRID);
        dto.setShpAreaGeography(shpAreaGeography);
        itGeomDbService.insertTGeomDb(dto);
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TGeomDb readGeom(String shpFileName, String geom, String fromRoute) {
        if (StringUtils.isEmpty(geom)) {
            log.error("读取geom失败");
            return null;
        }

        TGeomDb dto = new TGeomDb();
        dto.setId(IdUtils.fastSimpleUUID());
        dto.setName(shpFileName);
        dto.setFiletype("1");
        dto.setFromroute(fromRoute);

        String ewkt = "SRID=" + shpDbSRID + ";" + geom;
        if (geom.toLowerCase().indexOf("srid") >= 0) {
            ewkt = geom;
        }
        Long sort = 0L;
        TGeomDbDetails detailsDTO = new TGeomDbDetails();
        detailsDTO.setShpDbSRID(shpDbSRID);
        detailsDTO.setShpAreaSRID(shpAreaSRID);
        detailsDTO.setShpOutSRID(shpOutSRID);
        detailsDTO.setShpAreaGeography(shpAreaGeography);
        detailsDTO.setId(IdUtils.fastSimpleUUID());
        detailsDTO.setUploadId(dto.getId());
        detailsDTO.setGeom(ewkt);
        detailsDTO.setSort(sort);
        detailsDTO.setGeomJson("{\"id\":\"" + detailsDTO.getId() + "\"}");
        itGeomDbDetailsService.insertTGeomDbDetails(detailsDTO);

        TGeomDb retDto = itGeomDbDetailsService.sumGeomDetailsByUploadId(dto.getId());
        dto.setGeom(retDto.getGeom());
        dto.setGeomNumber(retDto.getGeomNumber());
        dto.setGeomArea(retDto.getGeomArea());
        dto.setShpDbSRID(shpDbSRID);
        dto.setShpAreaSRID(shpAreaSRID);
        dto.setShpOutSRID(shpOutSRID);
        dto.setShpAreaGeography(shpAreaGeography);
        itGeomDbService.insertTGeomDb(dto);
        return dto;
    }


    private String convertShpFieldType2H2GISOrPG(Class value) throws Exception {
        if (value == String.class) {// 文本
            return "varchar";
        }
        if (value == Integer.class) {// 短整型
            return "int";
        }
        if (value == Long.class) {// 长整型
            return "bigint";
        }
        if (value == Double.class || value == Float.class) {// 浮点型 双精度 保留精度，比如在金币上运算更安全
            return "numeric";
        }
        if (value == Date.class) {
            return "timestamp without time zone";// 日期, 使用包含时区的来处理
        }
        if (Geometry.class.isAssignableFrom(value)) {
            return "public.geometry";
        }
        // 除了上述，真不知道还会有啥了。除非arcgis的shp又增加了新类型？！那无能为力了，抛出异常吧
        throw new Exception("不支持的类型！" + value.getName());
    }

    public List<Map<String, Object>> readShapeFile(String shpFile) {
        return readShapeFile(new File(shpFile));
    }

    /**
     * @param shpFile 传递的是shape文件中的.shp文件
     */
    public List<Map<String, Object>> readShapeFile(File shpFile) {
        /**
         * 直接使用shapefileDatastore,如果不知道，也可以使用工厂模式(见下个方法)
         * 建议，如果确定是shape文件，就直使用shapefileDatastore
         */
        ShapefileDataStore shapefileDataStore = null;
        SimpleFeatureIterator simpleFeatureIterator = null;
        List<Map<String, Object>> retFeatureList = new ArrayList<>();
        String tableId = null;
        try {
            shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());
            shapefileDataStore.setCharset(Charset.forName(readShpBM(shpFile.getAbsolutePath())));
            // 这个typeNamae不传递，默认是文件名称
            FeatureSource featuresource = shapefileDataStore.getFeatureSource(shapefileDataStore.getTypeNames()[0]);

            // 获取 srid
            CoordinateReferenceSystem crs = featuresource.getSchema().getCoordinateReferenceSystem();
            Integer srid = CRS.lookupEpsgCode(crs, true);
            if (srid == null) {
                throw new Exception("SHP文件坐标系未查到");
            }

            // 获取当前数据的geometry类型（点、线、面）
//            GeometryType geometryType = featuresource.getSchema().getGeometryDescriptor().getType();
//            System.out.println(geometryType.getName());
//            if ("Polygon".equals(geometryType.get) {
//                tb.add("the_geom", Polygon.class);
//            } else if ("MultiPolygon".equals(geomTypeData.getGeometryType())) {
//                tb.add("the_geom", MultiPolygon.class);
//            }
            // 读取要素
            SimpleFeatureCollection simpleFeatureCollection = (SimpleFeatureCollection) featuresource.getFeatures();
            // 获取当前矢量数据有哪些属性字段值

            // 获取字段列表
            List<String> attributesList = new ArrayList<>();
            List<Map<String, String>> tableCommsList = new ArrayList<>();
            List<AttributeDescriptor> attributes = simpleFeatureCollection.getSchema().getAttributeDescriptors();
            for (AttributeDescriptor h : attributes) {
                attributesList.add(h.getLocalName());
//                String type = convertShpFieldType2H2GISOrPG(h.getType().getBinding());
//                Map<String, String> attributesMap = new LinkedHashMap<>();
//                if (!h.getLocalName().toLowerCase().equals("the_geom")) {
//                    attributesMap.put("key", h.getLocalName().toLowerCase());
//                    attributesMap.put("value", type);
//                    tableCommsList.add(attributesMap);
//                }
            }

            // 获取属性
            simpleFeatureIterator = simpleFeatureCollection.features();
            while (simpleFeatureIterator.hasNext()) {
                Map<String, Object> pg_rk = new LinkedHashMap<>();
                SimpleFeature simpleFeature = simpleFeatureIterator.next();
                int f_i = 0;
                for (; f_i < attributesList.size(); f_i++) {
                    if ("the_geom".equals(attributesList.get(f_i))) {
                        Geometry geometry = (Geometry) simpleFeature.getAttribute(attributesList.get(f_i));
                        String geom = geometry.toText();
                        String ewkt = "SRID=" + srid + ";" + geom;
                        pg_rk.put(attributesList.get(f_i), ewkt);
                    } else {
                        pg_rk.put(attributesList.get(f_i).toLowerCase(), simpleFeature.getAttribute(attributesList.get(f_i)));
                    }

                }
                retFeatureList.add(pg_rk);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (simpleFeatureIterator != null) {
                simpleFeatureIterator.close();
            }
            if (shapefileDataStore != null) {
                shapefileDataStore.dispose();
            }
        }
        return retFeatureList;
    }

    private String readShpBM(String shpPath) {
        String dbfPath = shpPath.substring(0, shpPath.length() - 4) + ".dbf";
        try (FileInputStream fis = new FileInputStream(dbfPath)) {
            // 跳过到第29字节
            fis.skip(29);
            int languageDriverId = fis.read();
            System.out.println(languageDriverId);

            // 根据语言驱动器ID判断编码
            String encoding = detectEncodingByLanguageDriver(languageDriverId);
            System.out.println("根据语言驱动器 ID 判断编码为: " + encoding);
            return encoding;
        } catch (IOException e) {
            System.err.println("读取 .dbf 文件失败: " + e.getMessage());
            return "GBK";
        }
    }

    private String detectEncodingByLanguageDriver(int id) {
        switch (id) {
            case 0:
                return "UTF-8";
            case 77:
                return "GBK";
            // 添加更多语言驱动器ID对应关系
            default:
                return "GBK";
        }
    }
}
